﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MoroMoro.Samples.ToDoList.Base.Domain.Expanders;

namespace MoroMoro.Samples.ToDoList.Base.Domain.Specifications
{
    /// <summary>
    /// ビジネスルールを仕様として表すための基本クラスです。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 仕様は、ビジネスルールのロジックを式ツリーで実装します。
    /// これにより、ロジックの実装の再利用性が大きく高まります。
    /// </para>
    /// <para>
    /// 仕様は特に、シーケンスの要素の選別条件として利用されます。
    /// <see cref="Enumerable.Where&lt;TSource&gt;(IEnumerable&lt;TSource&gt;, Func&lt;TSource, Boolean&gt;)">IEnumerable&lt;T&gt;.Where</see> 拡張メソッドや 
    /// <see cref="Queryable.Where&lt;TSource&gt;(IQueryable&lt;TSource&gt;, Expression&lt;Func&lt;TSource, Boolean&gt;&gt;)">IQueryable&lt;T&gt;.Where</see> 拡張メソッド等、
    /// 述語を引数として取るメソッドに仕様を渡すことができます。
    /// </para>
    /// <para>
    /// 仕様は、複数の仕様を組み合わせて新しい仕様を生成することができます。
    /// 例えば、二つの仕様を満たす場合に真となる仕様を生成するには、<see cref="Specification&lt;TEntity&gt;.And(Specification&lt;TEntity&gt;)"/> メソッドや 
    /// <see cref="Specification&lt;TEntity&gt;.operator&amp;(Specification&lt;TEntity&gt;, Specification&lt;TEntity&gt;)"/> 演算子が使用できます。
    /// </para>
    /// <para>
    /// 仕様を使用して値を返すエンティティのプロパティがある場合、
    /// その仕様では <see cref="IExpandable&lt;TEntity&gt;"/> インターフェイスを実装する必要があります。
    /// 仕様や視点を使用して値を返すエンティティのプロパティを式ツリー内で使用する仕様では、
    /// そのプロパティに対する <see cref="IExpandable&lt;TEntity&gt;"/> 実装を元に生成した <see cref="Expander&lt;TEntity&gt;"/> を返すように、
    /// <see cref="Specification&lt;TEntity&gt;.GetExpander()"/> メソッドをオーバーライドします。
    /// 詳細については、<see cref="IExpandable&lt;TEntity&gt;"/> を参照してください。
    /// </para>
    /// </remarks>
    /// <example>
    /// 仕様の実装方法を次のコード例に示します。
    /// <code>
    /// using System;
    /// using System.Linq.Expressions;
    /// using MoroMoro.Samples.ToDoList.Base.Domain.Specifications;
    /// using MoroMoro.Samples.ToDoList.Base.Domain.Expanders;
    /// 
    /// namespace MoroMoro.Samples.ToDoList.Domain.ToDos
    /// {
    ///     [Serializable]
    ///     public sealed class ToDoStateSpecification : Specification&lt;ToDo&gt;
    ///     {
    ///         public ToDoStateSpecification(ToDoState state)
    ///         {
    ///             _state = state;
    ///         }
    /// 
    /// 
    ///         private readonly ToDoState _state;
    /// 
    /// 
    ///         protected override Expression&lt;Func&lt;ToDo, bool&gt;&gt; CreateExpression()
    ///         {
    ///             return entity => (entity.State == _state);
    ///         }
    /// 
    /// 
    ///         // ToDoStateViewpoint を使用して値を返している ToDo.State プロパティが式ツリーに含まれるため GetExpander をオーバーライドする.
    ///         protected override Expander&lt;ToDo&gt; GetExpander()
    ///         {
    ///             return new Expander&lt;ToDo&gt;(new ToDoStateViewpoint());
    ///         }
    ///     }
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    /// <seealso cref="IExpandable&lt;TEntity&gt;"/>
    /// <seealso cref="Viewpoints.Viewpoint&lt;TEntity, TValue&gt;"/>
    [Serializable]
    public abstract class Specification<TEntity>
        where TEntity : class
    {
        /// <summary>
        /// 常に真を示す仕様を取得します。
        /// </summary>
        public static readonly Specification<TEntity> True = new TrueSpecification<TEntity>();

        /// <summary>
        /// 常に偽を示す仕様を取得します。
        /// </summary>
        public static readonly Specification<TEntity> False = new FalseSpecification<TEntity>();


        /// <summary>
        /// <see cref="Specification&lt;TEntity&gt;"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        public Specification()
        {
            _expression = null;
            _delegate = null;
        }


        /// <summary>
        /// 仕様を表現する式ツリー。
        /// </summary>
        private Expression<Func<TEntity, bool>> _expression;

        /// <summary>
        /// 仕様を表現するデリゲート。
        /// </summary>
        private Func<TEntity, bool> _delegate;


        /// <summary>
        /// 仕様を表現する式ツリーを取得します。。
        /// </summary>
        public Expression<Func<TEntity, bool>> Expression
        {
            get
            {
                if (_expression == null)
                {
                    _expression = CreateExpandedExpression();
                }
                return _expression;
            }
        }

        /// <summary>
        /// 仕様を表現するデリゲートを取得します。
        /// </summary>
        public Func<TEntity, bool> Delegate
        {
            get
            {
                if (_delegate == null)
                {
                    _delegate = Expression.Compile();
                }
                return _delegate;
            }
        }


        /// <summary>
        /// 指定したエンティティがこの仕様を満たすかどうかを示す値を取得します。
        /// </summary>
        /// <param name="target">エンティティ。</param>
        /// <returns>指定したエンティティがこの仕様を満たす場合は true、それ以外は false。</returns>
        public bool IsSatisfiedBy(TEntity target)
        {
            return Delegate(target);
        }


        /// <summary>
        /// 仕様を表現する式ツリーを生成します。
        /// </summary>
        /// <returns>仕様を表現する式ツリー。</returns>
        protected abstract Expression<Func<TEntity, bool>> CreateExpression();

        /// <summary>
        /// 仕様を表現する式ツリーに展開可能なプロパティが使用されている場合に、式ツリーを展開するためのエキスパンダを取得します。
        /// エキスパンダが null の場合、展開は行われません。
        /// 既定の実装では null を返します。
        /// </summary>
        /// <returns>エキスパンダ。</returns>
        protected virtual Expander<TEntity> GetExpander()
        {
            return null;
        }

        /// <summary>
        /// 仕様を表現する展開済みの式ツリーを生成します。
        /// </summary>
        /// <returns>仕様を表現する展開済みの式ツリー。</returns>
        private Expression<Func<TEntity, bool>> CreateExpandedExpression()
        {
            var source = CreateExpression();
            var expander = GetExpander();
            return (expander != null) ? expander.Expand(source) : source;
        }


        /// <summary>
        /// この仕様と指定した仕様の両方を満たすことで真となる仕様を生成します。
        /// </summary>
        /// <param name="other">仕様。null の場合は常に真を示す仕様で置き換えられます。</param>
        /// <returns>この仕様と指定した仕様の両方を満たすことで真となる仕様。</returns>
        public Specification<TEntity> And(Specification<TEntity> other)
        {
            return new AndSpecification<TEntity>(this, other);
        }

        /// <summary>
        /// この仕様と指定した仕様のいずれかを満たすことで真となる仕様を生成します。
        /// </summary>
        /// <param name="other">仕様。null の場合は常に偽を示す仕様で置き換えられます。</param>
        /// <returns>この仕様と指定した仕様のいずれかを満たすことで真となる仕様。</returns>
        public Specification<TEntity> Or(Specification<TEntity> other)
        {
            return new OrSpecification<TEntity>(this, other);
        }

        /// <summary>
        /// この仕様が満たされない場合に真、満たされる場合に偽となる仕様を生成します。
        /// </summary>
        /// <returns>この仕様が満たされない場合に真、満たされる場合に偽となる仕様。</returns>
        public Specification<TEntity> Reverse()
        {
            return new NotSpecification<TEntity>(this);
        }


        /// <summary>
        /// 指定した二つの仕様の両方を満たすことで真となる仕様を生成します。
        /// </summary>
        /// <param name="left">一つ目の仕様。null の場合は常に真を示す仕様で置き換えられます。</param>
        /// <param name="right">二つ目の仕様。null の場合は常に真を示す仕様で置き換えられます。</param>
        /// <returns>指定した二つの仕様の両方を満たすことで真となる仕様。</returns>
        public static Specification<TEntity> operator &(Specification<TEntity> left, Specification<TEntity> right)
        {
            return new AndSpecification<TEntity>(left, right);
        }

        /// <summary>
        /// 指定した二つの仕様のいずれかを満たすことで真となる仕様を生成します。
        /// </summary>
        /// <param name="left">一つ目の仕様。null の場合は常に偽を示す仕様で置き換えられます。</param>
        /// <param name="right">二つ目の仕様。null の場合は常に偽を示す仕様で置き換えられます。</param>
        /// <returns>指定した二つの仕様のいずれかを満たすことで真となる仕様。</returns>
        public static Specification<TEntity> operator |(Specification<TEntity> left, Specification<TEntity> right)
        {
            return new OrSpecification<TEntity>(left, right);
        }

        /// <summary>
        /// 指定した仕様が満たされない場合に真、満たされる場合に偽となる仕様を生成します。
        /// </summary>
        /// <param name="source">仕様。</param>
        /// <returns>指定した仕様が満たされない場合に真、満たされる場合に偽となる仕様。</returns>
        /// <exception cref="System.ArgumentNullException">source が null です。</exception>
        public static Specification<TEntity> operator !(Specification<TEntity> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            return new NotSpecification<TEntity>(source);
        }
    }
}
